﻿/*

The MIT License

Copyright (c) 2010 Cartesian Analytics, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;
using System.Diagnostics;

namespace Pipra.Math.Geometry
{
    /// <summary>
    /// A point in 2D space.
    /// </summary>
    public struct Point2 :
        IPoint2<double>,
        IComparable<Point2>,
        IEquatable<Point2>,
        IIntersectable<Point2>,
        IContainable<Mbr>,
        IHasDistance<double,Point2>
    {

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        /// <returns>True if both points have the same component values.</returns>
        public static bool operator ==(Point2 a, Point2 b)
        {
            return a.Equals(b);
        }
        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        /// <returns>True if both points do not have the same component values.</returns>
        public static bool operator !=(Point2 a, Point2 b)
        {
            return !a.Equals(b);
        }
        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="p">A point.</param>
        /// <param name="v">A vector.</param>
        /// <returns>The result.</returns>
        public static Point2 operator+(Point2 p, Vector2 v)
        {
            return p.Add(v);
        }
        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="v">A vector.</param>
        /// <param name="p">A point.</param>
        /// <returns>The result.</returns>
        public static Point2 operator+(Vector2 v, Point2 p)
        {
            return p.Add(v);
        }
        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A coordinate.</param>
        /// <returns>The result.</returns>
        public static Point2 operator+(Point2 a, ICoordinatePair<double> b)
        {
            return a.Add(b);
        }
        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="a">A coordinate.</param>
        /// <param name="b">A point.</param>
        /// <returns>The result.</returns>
        public static Point2 operator +(ICoordinatePair<double> a, Point2 b)
        {
            return b.Add(a);
        }
        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A point.</param>
        /// <returns>The result.</returns>
        public static Vector2 operator -(Point2 a, Point2 b)
        {
            return a.Difference(b);
        }
        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A coordinate.</param>
        /// <returns>The result.</returns>
        public static Vector2 operator -(Point2 a, Vector2 b)
        {
            return a.Difference(b);
        }
        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="a">A point.</param>
        /// <param name="b">A coordinate.</param>
        /// <returns>The result.</returns>
        public static Vector2 operator -(Point2 a, ICoordinatePair<double> b)
        {
            return a.Difference(b);
        }

        /// <summary>
        /// A point with all components set to zero.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static readonly Point2 ZeroPoint = new Point2(0,0);
        /// <summary>
        /// An invalid point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        public static readonly Point2 Invalid = new Point2(Double.NaN, Double.NaN);

        /// <summary>
        /// The X componenet of this point.
        /// </summary>
        public readonly double X;
        /// <summary>
        /// The Y componenet of this point.
        /// </summary>
        public readonly double Y;
    
        /// <summary>
        /// Creates a point with the given <paramref name="x"/> and <paramref name="y"/> coordinates.
        /// </summary>
        /// <param name="x">A coordinate.</param>
        /// <param name="y">A coordinate.</param>
        public Point2(double x, double y) {
			X = x;
			Y = y;
		}
        /// <summary>
        /// Creates a point with the same cordinates as the given <paramref name="point"/>.
        /// </summary>
        /// <param name="point">A coordinate pair.</param>
		public Point2(ICoordinatePair<double> point) {
            X = point.X;
            Y = point.Y;
		}

        /// <summary>
        /// The X componenet of this point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        double ICoordinatePair<double>.X
        {
            get { return X; }
        }
        /// <summary>
        /// The Y componenet of this point.
        /// </summary>
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        double ICoordinatePair<double>.Y
        {
            get { return Y; }
        }
        /// <summary>
        /// Determines the difference between this point and another <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to take the difference from this point.</param>
        /// <returns>The vector difference.</returns>
        public Vector2 Difference(Point2 point)
        {
            return new Vector2(
                X - point.X,
                Y - point.Y
            );
        }
        /// <summary>
        /// Determines the difference between this point and another <paramref name="point"/> as a vector.
        /// </summary>
        /// <param name="point">The point as a vector to take the difference from this point.</param>
        /// <returns>The vector difference.</returns>
        public Vector2 Difference(Vector2 point)
        {
            return new Vector2(
                X - point.X,
                Y - point.Y
            );
        }
        /// <summary>
        /// Determines the difference between this point and another <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to take the difference from this point.</param>
        /// <returns>The vector difference.</returns>
        public Vector2 Difference(ICoordinatePair<double> point)
        {
            return new Vector2(
                X - point.X,
                Y - point.Y
            );
        }
        /// <summary>
        /// Determines the difference between this point and another <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to take the difference from this point.</param>
        /// <returns>The vector difference.</returns>
        IVector2<double> IPoint2<double>.Difference(ICoordinatePair<double> point)
        {
            return Difference(point);
        }
        /// <summary>
        /// Adds an offset vector to this point.
        /// </summary>
        /// <param name="delta">The offset vector.</param>
        /// <returns>A point offset from this point by a <paramref name="delta"/>.</returns>
        public Point2 Add(Vector2 delta)
        {
            return new Point2(X + delta.X, Y + delta.Y);
        }
        /// <summary>
        /// Adds the coordinate values of the given point to this point.
        /// </summary>
        /// <param name="delta">A point.</param>
        /// <returns>A point offset from this point by a <paramref name="delta"/>.</returns>
        public Point2 Add(Point2 delta)
        {
            return new Point2(X + delta.X, Y + delta.Y);
        }
        /// <summary>
        /// Adds an offset vector to this point.
        /// </summary>
        /// <param name="delta">The offset vector.</param>
        /// <returns>A point offset from this point by a <paramref name="delta"/>.</returns>
        public Point2 Add(ICoordinatePair<double> delta)
        {
            return new Point2(X + delta.X, Y + delta.Y);
        }
        /// <summary>
        /// Adds an offset vector to this point.
        /// </summary>
        /// <param name="delta">The offset vector.</param>
        /// <returns>A point offset from this point by a <paramref name="delta"/>.</returns>
        IPoint2<double> IPoint2<double>.Add(ICoordinatePair<double> delta)
        {
            return Add(delta);
        }
        /// <summary>
        /// Calculates the distance between this and the given <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(Point2 point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            return System.Math.Sqrt(
                (dx * dx)
                +
                (dy * dy)
            );
        }
        /// <summary>
        /// Calculates the distance between this and the given <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to calculate distance to.</param>
        /// <returns>The distance.</returns>
        public double Distance(IPoint2<double> point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            return System.Math.Sqrt(
                (dx * dx)
                +
                (dy * dy)
            );
        }
        /// <summary>
        /// Calculates the squared distance between this and the given <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(Point2 point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            return (
                (dx * dx)
                +
                (dy * dy)
            );
        }
        /// <summary>
        /// Calculates the squared distance between this and the given <paramref name="point"/>.
        /// </summary>
        /// <param name="point">The point to calculate squared distance to.</param>
        /// <returns>The squared distance.</returns>
        public double DistanceSquared(IPoint2<double> point)
        {
            double dx = point.X - X;
            double dy = point.Y - Y;
            return (
                (dx * dx)
                +
                (dy * dy)
            );
        }

        /// <summary>
        /// Indicates whether this instance and a specified object are equal.
        /// </summary>
        /// <returns>
        /// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
        /// </returns>
        /// <param name="obj">Another object to compare to.</param><filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            return null != obj
                && (
                    (obj is Point2 && Equals((Point2)obj))
                    ||
                    (obj is ICoordinatePair<double> && Equals(obj as ICoordinatePair<double>))
                )
            ;
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return X.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return String.Concat(X, ',', Y);
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(Point2 other)
        {
            return X == other.X && Y == other.Y;
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public bool Equals(ICoordinatePair<double> other)
        {
            return !ReferenceEquals(null,other) && X == other.X && Y == other.Y;
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: 
        ///                     Value 
        ///                     Meaning 
        ///                     Less than zero 
        ///                     This object is less than the <paramref name="other"/> parameter.
        ///                     Zero 
        ///                     This object is equal to <paramref name="other"/>. 
        ///                     Greater than zero 
        ///                     This object is greater than <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public int CompareTo(Point2 other)
        {
            int c = X.CompareTo(other.X);
            return 0 == c ? Y.CompareTo(other.Y) : c;
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings: 
        ///                     Value 
        ///                     Meaning 
        ///                     Less than zero 
        ///                     This object is less than the <paramref name="other"/> parameter.
        ///                     Zero 
        ///                     This object is equal to <paramref name="other"/>. 
        ///                     Greater than zero 
        ///                     This object is greater than <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">An object to compare with this object.</param>
        public int CompareTo(ICoordinatePair<double> other)
        {
            if (null == other)
            {
                return 1;
            }
            int c = X.CompareTo(other.X);
            return 0 == c ? Y.CompareTo(other.Y) : c;
        }

        bool IIntersectable<Point2>.Intersects(Point2 p)
        {
            return Equals(p);
        }

        bool IIntersectable<IPoint2<double>>.Intersects(IPoint2<double> p)
        {
            return Equals(p);
        }

        IMbr<double> IHasMbr<double>.GetMbr()
        {
            return new Mbr(this);
        }

        IPoint2<double> IHasPlanarCentroid<double>.GetCentroid()
        {
            return this;
        }
        /// <summary>
        /// Determines if the given MBR contains this point.
        /// </summary>
        /// <param name="mbr">The MBR to test.</param>
        /// <returns>True if this point is within the given MBR.</returns>
        public bool Within(Mbr mbr)
        {
            return mbr.Intersects(this);
        }
        /// <summary>
        /// Determines if the given MBR contains this point.
        /// </summary>
        /// <param name="mbr">The MBR to test.</param>
        /// <returns>True if this point is within the given MBR.</returns>
        public bool Within(IMbr<double> mbr)
        {
            return mbr.Intersects(this);
        }
    }
}
